package kbs.scmconsole
{
    import mx.containers.TitleWindow;
    import mx.containers.VBox;
    import mx.containers.VDividedBox;
    import mx.controls.Label;
    import mx.controls.TextArea;
    import mx.controls.Button;
    import mx.core.UIComponent;
    import mx.utils.ObjectUtil;
    import mx.managers.PopUpManager;
    import flash.events.KeyboardEvent;
    import mx.controls.textClasses.TextRange;
    import kbs.scm.Eval;
    import kbs.scm.Env;
    import kbs.scm.Base;

    public class Console extends TitleWindow
    {
        private var m_dump:TextArea;
        private var m_input:TextArea;
        private var m_history:Array = [];
        private var m_historyidx:int = 0;
        public var m_evaluator:Eval = null;

        public function Console()
        {
            super();
            title = "Scheme Console";
            showCloseButton = true;
            addEventListener("close", closeme);
            var vb:VBox = new VBox();
            addChild(vb);

            var vdb:VDividedBox = new VDividedBox();
            vb.addChild(vdb);

            m_dump = new TextArea();
            m_dump.id = "dump";
            m_dump.height = 300;
            m_dump.width = 500;
            m_dump.setStyle("fontSize", "14");
            m_dump.editable = false;
            m_dump.addEventListener
                ("valueCommit",
                 function (o:Object):void {
                    m_dump.verticalScrollPosition =
                        m_dump.maxVerticalScrollPosition;
                });

            vdb.addChild(m_dump);

            m_input = new TextArea();
            m_input.id = "input";
            m_input.height = 200;
            m_input.width = 500;
            m_input.setStyle("fontSize", 14);
            // Some sample code for kicks
            m_input.text =
                "(define my-button (mx.controls.Button.))\n"+
                "(.label$ my-button \"Click me!\")\n"+
                "(.addChild workspace my-button)";

            vdb.addChild(m_input);

            var b:Button = new Button();
            b.label = "Eval";
            b.addEventListener("click", runit);
            vb.addChild(b);

            m_input.addEventListener(KeyboardEvent.KEY_UP, hotkeysExecute);
            for (var i:int=0; i<20; i++) {
                m_history[i] = "";
            }
            addEventListener("creationComplete", cc);
        }

        private function closeme(o:Object):void
        { PopUpManager.removePopUp(this); }

        private function cc(o:Object):void
        { m_input.setFocus(); }

        private function addHistory(s:String):void
        {
            m_historyidx++;
            if (m_historyidx >= 20) { m_historyidx = 0; }
            m_history[m_historyidx] = s;
        }

        private function nextHistory():String
        {
            var ret:String = "";
            for (var i:int=0; i<20; i++) {
                ret = _nextHistory();
                if (ret != "") break;
            }
            return ret;
        }

        private function prevHistory():String
        {
            var ret:String = "";
            for (var i:int=0; i<20; i++) {
                ret = _prevHistory();
                if (ret != "") break;
            }
            return ret;
        }
        
        private function _nextHistory():String
        {    
            m_historyidx++;
            if (m_historyidx >= 20) { m_historyidx = 0; }
            return m_history[m_historyidx];
        }

        private function _prevHistory():String
        {
            var ret:String = m_history[m_historyidx--];
            if (m_historyidx < 0) { m_historyidx = 19; }
            return ret;
        }

        private function runit(o:Object):void
        {
            var s:String = m_input.text;
            m_input.text = "";

            m_dump.text += "\n";
            m_dump.text += s;
            m_dump.text += "\n";
            tryit(s, m_evaluator.m_global);
        }

        private function hotkeysExecute(event:KeyboardEvent):void
        {
            var tr:TextRange = new TextRange(m_input, false);
            if (m_input.length > 0) {
                tr.beginIndex = 0; tr.endIndex = m_input.length;
                tr.fontWeight = "normal";
            }
            if (event.ctrlKey) {
                // control-p == prev
                if (event.charCode == 112) {
                    m_input.text = prevHistory();
                    m_input.setSelection
                        (m_input.text.length, m_input.text.length);
                }
                // control-n == next
                else if (event.charCode == 110) {
                    m_input.text = nextHistory();
                    m_input.setSelection
                        (m_input.text.length, m_input.text.length);
                }
            }
            else if (event.charCode == 41) { // )
                // search backwards for a matching paren
                var curpos:int = m_input.selectionEndIndex;
                var tpos:int = findMatchingParen(m_input.text, curpos);
                if (tpos >= 0) {
                    tr.beginIndex = tpos;
                    tr.endIndex = tpos+1;
                    tr.fontWeight = "bold";
                }
            }
        }

        private function findMatchingParen(s:String, end:int):int
        {
            var ret:int = end-1;
            var skip:Boolean = false;
            var nest:int = 0;
            while (ret >= 0) {
                var cc:int = s.charCodeAt(ret);
                if (skip) {
                    if (cc == 34) { /* "" */ skip = false; }
                }
                else {
                    if (cc == 41) { /* // ) */ nest++; }
                    else if (cc == 40) { /* ( */
                        nest--;  if (nest == 0) { return ret; }
                    }
                    else if (cc == 34) { /* "" */ skip = true; }
                }
                ret --;
            }
            return ret;
        }

        private function tryit(s:String, env:Env):void
        {
            addHistory(s);
            var r:Base = m_evaluator.loadString(s, env);
            m_dump.text += m_evaluator.dump(r);
            m_dump.text += "\n\n";
            m_input.setFocus();
        }
    }
}
